﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;

namespace Dwarf.Managed
{
    /// <summary>Class for command line settings processing, IO management</summary>
    /// 
    public class Configurator
    {
        private Settings settings;
        private StreamWriter profileWriter;
        private StreamReader inputReader;

        private char[] patternContent;                      // internal storage of pattern

        private const int maximumStringLength = 50000000;   // maximum size of one content part
        private int contentPartsIterator = 1;               // internal content parts iterator
        private int contentPartsSize = 1;                   // count of content parts
        private int fileSize;                               // estimated size of content part

        private char[] buffer;
        private char[] bufferNext;
        private int bufferNextPosition = 0;

        /// <summary>Cli parameters initialization</summary>
        /// <param name="args">Command line parameters</param>
        /// <param name="threadCount">Default count of threads in processing</param>
        /// 
        public Configurator(string[] args)
        {
            settings = new Settings();
            settings.GetSettings(args);
            if (settings.threadCount == 0)
            {
                settings.threadCount = System.Environment.ProcessorCount;     // if command line parameter 'threadCount' is not specified, fill it by default
            }
            if (settings.profileFile != "")
            {
                profileWriter = new StreamWriter(new FileStream(settings.profileFile, FileMode.Create, FileAccess.Write, FileShare.Write));
            }
        }

        /// <summary>Write settings</summary>
        ///
        public void WriteSettings()
        {
            String stringSettings = settings.StringSettings();
            Console.WriteLine(stringSettings);               // write to console
            if (profileWriter != null)                       // write to profile file
            {
                profileWriter.WriteLine(stringSettings);
            }
        }

        /// <summary>Close the profile and result files</summary>
        /// <param name="time">Execution time</param>
        /// <param name="positions">List of pattern positions found</param>
        ///
        public void Close(double time, List<int> positions)
        {
            String timeResult = "Clock time   (sec): " + time.ToString("F8");
            Console.WriteLine(timeResult);
            if (profileWriter != null)                      // write to profile file
            {
                profileWriter.WriteLine(timeResult);
                profileWriter.Close();
            }

            if (settings.resultFile != "")                  // write to outputfile
            {
                StreamWriter resultWriter = new StreamWriter(new FileStream(settings.resultFile, FileMode.Create, FileAccess.Write, FileShare.Write));
                for (int i = 0; i < positions.Count; i++)
                {
                    resultWriter.WriteLine("Position: " + positions.ElementAt(i));   // write all the positions values to the outputfile
                }
                resultWriter.Close();
            }
        }

        /// <summary>Get cityCount and fill distances[cityCount, cityCount] matrix</summary>
        /// <param name="pattern">Pattern to be found</param>
        /// <param name="threadCount">Count of processors</param>
        /// <returns>Count of strings, that content has to be devided</returns>
        ///
        public int GetData(ref string pattern, int threadCount)
        {
            // Open the file to read from.
            FileStream stream = null;
            inputReader = null;
            try
            {
                stream = new FileStream(settings.inputFile, FileMode.Open, FileAccess.Read, FileShare.Read);
                inputReader = new StreamReader(stream); // initializaton of input stream
                int currentChar = inputReader.Read();
                while (currentChar != 1)                // while the current symbol is not equal to delimeter ((char)'1') read the pattern
                {
                    pattern += (char)currentChar;
                    currentChar = inputReader.Read();
                }
                patternContent = pattern.ToCharArray();
                contentPartsSize = threadCount;
                long fileSizeLong = stream.Length / threadCount; // initialization of string partition
                while (fileSizeLong > maximumStringLength)
                {
                    fileSizeLong = 1 + fileSizeLong / 2;
                    contentPartsSize = contentPartsSize * 2;
                }
                fileSize = (int)fileSizeLong;
            }
            catch (Exception e)
            {
                Console.WriteLine("Cannot correctly read the file: {0}", e.Message);
            }
            return contentPartsSize;
        }

        /// <summary>Read the next content part maximized by length from input file</summary>
        /// <returns>String representation of the next content part</returns>
        ///
        public String GetNextInputPart()
        {
            if (contentPartsIterator < contentPartsSize)
            {
                contentPartsIterator++;
                return GetMiddleInputPart();
            }
            else if (contentPartsIterator == contentPartsSize)
            {
                contentPartsIterator++;
                return GetLastInputPart();
            }
            else
            {
                return null;
            }
        }

        /// <summary>Read the next but not the last content part maximized by length from input file</summary>
        /// <returns>String representation of the part</returns>
        ///
        private String GetMiddleInputPart()
        {
            if (bufferNext == null)
            {
                buffer = new char[fileSize + 1];
            }
            else
            {
                for (int i = 0; i < bufferNextPosition; i++)
                {
                    buffer[i] = bufferNext[i];
                }
            }

            inputReader.ReadBlock(buffer, bufferNextPosition, fileSize - bufferNextPosition);
            
            bufferNextPosition = 0;
            bufferNext = new char [fileSize];
            while (patternContent.Contains(buffer[fileSize - bufferNextPosition - 1]))
            {
                bufferNextPosition++;
            }

            for (int i = 0; i < bufferNextPosition; i++)
            {
                bufferNext[i] = buffer[fileSize - bufferNextPosition - 1 + i];
            };
            buffer[fileSize] = (char)bufferNextPosition;
            return new String(buffer);
        }

        /// <summary>Read the rest from input file</summary>
        /// <returns>String representation of the part</returns>
        ///
        private String GetLastInputPart()
        {
            String bufferRest = new String(bufferNext, 0, bufferNextPosition);
            return (bufferRest + inputReader.ReadToEnd());
        }

        /// <summary>Print the states table</summary>
        /// <param name="states">States table</summary>
        /// <param name="stateDimensionChars">X-dimension of the states table</summary>
        /// <param name="stateDimensionCoincidence">Y-dimension of the states table</summary>
        /// 
        private void PrintStates(int[,] states, int stateDimensionChars, int stateDimensionCoincidence)
        {
            for (int i = 0; i < stateDimensionChars; i++)
            {
                for (int j = 0; j < stateDimensionCoincidence; j++)
                {
                    System.Console.Write(states[i, j]);             // just print
                }
            }
        }

        public int GetThreadCount()
        {
            return settings.threadCount;
        }
    }
}